#include "bulk_storage.h"

/* Error handling libraries. */
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <malloc.h>

/**
   The memStore provides a bulk_storage implementation backed by an array in memory.
   @see store
*/

struct memStore {
  void ** store;
  size_t blockSize;
  off_t blockCount;
  /** Always one greater than the last block returned by newBlock. */
  off_t newLastBlock;
  int calledFree;
  long total;
};

const struct store_functions memory_functions = {
  (void   (*)(void *))         &ms_close,
  (void * (*)(void *, off_t))  &ms_readBlock,
  (void   (*)(void *, off_t))  &ms_writeBlock,
  (off_t  (*)(void *       ))  &ms_newBlock,
  (void   (*)(void *, off_t))  &ms_releaseBlock,
  (void   (*)(void *, off_t))  &ms_freeBlock,
  (size_t (*)(void *))         &ms_blockSize
};


struct store * store_open_memory(size_t blockSize, off_t blockCount) {
  struct store * store = malloc(sizeof(struct store));
  store->functions = &memory_functions;
  store->store_p = ms_open(blockSize, blockCount);
  return store;

}

/**
   Expand the memStore's capacity.  Implemented by calloc'ing a new
   block of memory, and using memcpy to copy the existing data.

   TODO: This function overwrites memory returned by calloc--could
   optimize this away.  So far, this doesn't seem to be a problem in
   pracitce.
*/
void ms_grow_store(struct memStore * ms, off_t newSize);

/**
   Search the memStore for an unused block.
*/
off_t  ms_find_free_block(struct memStore * ms, off_t startBlock, off_t Stopblock);

struct memStore * ms_open(size_t blockSize, off_t blockCount) {
  struct memStore * ms = (struct memStore *) malloc(sizeof(struct memStore));

  ms->store = (void **)calloc(blockCount, sizeof(void **));
  ms->blockSize = blockSize;
  ms->blockCount = blockCount;
  ms->newLastBlock = 0;
  ms->calledFree = 0;
  ms->total = 0;

  return ms;
}

void ms_close(struct memStore * ms) {
  off_t i;
  int free_count = 0;
  for(i = 0; i < ms->blockCount; i++) {
    if(ms->store[i] != 0) {
      debug(TRIVIAL, ("O %d = %lx\n", (int)i, (intptr_t)ms->store[i]));
      ms_freeBlock(ms, i);
      free_count++;
    } else{
      debug(TRIVIAL, ("X %d = %lx\n", (int)i, (intptr_t)ms->store[i]));
    }
  }
  free(ms);

  debug(INFO, ("ms_close freed %d of %d blocks.\n", free_count, (int)ms->blockCount));
}

void * ms_readBlock(struct memStore * ms, off_t blockNumber) {

  ms->total++;
  assert ((blockNumber >= 0) && (blockNumber < ms->blockCount));
  return ms->store[blockNumber];
}

void ms_releaseBlock(const struct memStore * ms, off_t blockNumber) {
  /* This function intentionally left blank. */
}

void ms_writeBlock(const struct memStore * ms, off_t blockNumber) {
  /* This function intentionally left blank. */
}


/**
   This function round-robins through the FAT looking for a free block.
   If none is found, it expands the fat and data files.

   This function keeps some state information, and skips unnecessary
   checks.
 */

off_t ms_newBlock(struct memStore * ms) {

  off_t newBlock = -1;
  off_t current_block = ms->newLastBlock;
  off_t initial_block = ms->newLastBlock;

  newBlock = ms_find_free_block(ms, current_block, ms->blockCount);

  if(newBlock != -1) {
    ms->store[newBlock] = malloc(ms->blockSize);
    ms->newLastBlock = newBlock + 1;
    return newBlock;
  }

  if(ms->calledFree) {
    debug(INFO, ("Checking to reclaim space.."));
    ms->calledFree = 0;
    current_block = 0;
    newBlock = ms_find_free_block(ms, current_block, initial_block+1);
    if(newBlock != -1) {
      ms->store[newBlock] = malloc(ms->blockSize);
      ms->newLastBlock = newBlock +1;
      return newBlock;
    }

    debug(INFO,("%lx", (intptr_t)ms->store[100000]));
    assert(0);
  }

  newBlock = ms->blockCount;
  ms->newLastBlock = newBlock+1;

  ms_grow_store(ms, ms->blockCount * 2);

  ms->store[newBlock] = malloc(ms->blockSize);

  return newBlock;

}


void ms_freeBlock(struct memStore * ms, off_t blockNumber) {
  void * block = ms_readBlock(ms, blockNumber);
  assert(block != 0);

  ms->store[blockNumber] = (void*)0; /* Remove soon-to-be dangling reference. */

  debug(TRIVIAL,("freeing %d now %lx\n", (int)blockNumber, (intptr_t)ms->store[blockNumber]));

  ms->calledFree = 1;

  free(block);
}

off_t ms_find_free_block(struct memStore * ms, off_t startBlock, off_t stopBlock) {
  off_t i;

  if(startBlock < 0) startBlock = 0;
  if(stopBlock < 0) stopBlock = 0;
  if(stopBlock > ms->blockCount) stopBlock = ms->blockCount;
  if(startBlock > stopBlock) startBlock = stopBlock;
#ifdef VERBOSE
  if(startBlock == 0)
    printf("find free: start = %ld, stop = %ld\n", (long int)startBlock, (long int)stopBlock);
#endif
  for(i = startBlock; i < stopBlock; i++) {
    if(ms->store[i] == 0) {
#ifdef VERBOSE
      printf("returning: %d\n", i);
#endif
      return i;
    }
  }
#ifdef VERBOSE
  printf("returning: -1\n", i+j);
#endif
  return -1;
}


void ms_grow_store(struct memStore * ms, off_t newCount) {

  void * oldStore = ms->store;

  debug(INFO, ("ms_grow_store(%d);\n", (int)newCount));

  assert(newCount > ms->blockCount);

  ms->store = calloc(newCount, ms->blockSize);
  ms->store = memcpy(ms->store, oldStore, ms->blockCount * sizeof(void**));
  ms->blockCount = newCount;
  free(oldStore);
}

size_t ms_blockSize(struct memStore * ms) {
  return ms->blockSize;
}
off_t ms_blockCount(struct memStore * ms) {
  return ms->blockCount;
}

void ms_print(struct memStore * ms) {

  printf("Memory requests: %ld\n", ms->total);
}
